JSの初心者にPromiseとasync/awaitの使い方を雑に説明してみる
こんにちは、CX事業本部の夏目です。
先日、Javascriptの非同期処理に登場するPromiseとasync/awaitをJS初心者に説明する機会がありました。
なので、今回は説明したことを再度噛み砕いてから共有しようと思います。
Promiseの使い方
基本的な使い方
function sleep(sec) { return new Promise((resolve, reject) => { setTimeout(() => { console.log(`wait: ${sec} sec`); resolve(sec); }, sec * 1000); }); } function main() { sleep(2).then((data) => { return sleep(data * 2); }).then((data) => { console.log(`sleep result: ${data}`) return new Promise((resolve, reject) => { resolve(); }); }).then((data) => { console.log(`empty call: ${data}`); return new Promise((resolve, reject) => { reject("failed"); }); }).then((data) => { console.log("not call 1"); }).catch((e) => { console.log(`catch: ${e}`); return new Promise((resolve) => { resolve(); }); }).then((data) => { console.log(`empty call take2: ${data}`); return new Promise(() => { throw new Error("throwed"); }); }).catch((e) => { console.log(`failed: ${e}`); return new Promise((resolve) => { resolve() }); }).catch((e) => { console.log("not call 2") }).finally(() => { console.log("finally"); }); } main();
$ node main.js wait: 2 sec wait: 4 sec sleep result: 4 empty call: undefined catch: failed empty call take2: undefined failed: Error: throwed finally
非同期処理を行いたい場合、Promiseのコンストラクタに渡す関数の中に記述します。
Promiseのコンストラクタに渡す関数は2つの引数を取ります。
第一引数resolve
, 第二引数reject
はどちらも1個もしくは0個の引数をとる関数です。
resolve
は非同期処理の成功時、reject
は失敗時に呼び出します。
注意すべきことは、これらはあくまで関数の呼び出しでしかないことです。
呼び出したからと言って、Promiseのコンストラクタに渡した関数が停止するわけではありません。
作成したPromiseオブジェクトに対して、基本的(?)に次の3つの関数を呼び出します。
また、これらの関数でメソッドチェーンを作って使います。
then((data) => {})
: Promiseオブジェクトのコンストラクタに渡した関数でresolve
が呼ばれたら、引数に渡した関数が実行されるcatch((e) => {})
: Promiseオブジェクトのコンストラクタに渡した関数でreject
が呼ばれたら、引数に渡した関数が実行されるfinally(() => {})
: メソッドチェーンで最後にかならず実行される
then()
やcatch()
は渡した関数でPromiseオブジェクトを渡せば、どんどんメソッドチェーンを伸ばすことができます。
複数の(非同期)処理を行うときなどチェーンを伸ばします。
catch()
はそれよりも前の部分で起きたエラーを拾います。
これはreject
が呼ばれたものだけではなく、throw
されたエラーであっても拾います。
Promise.all()
function sleep(sec) { return new Promise((resolve, reject) => { setTimeout(() => { console.log(`wait: ${sec} sec`); resolve(sec); }, sec * 1000); }); } function main() { Promise.all([ sleep(3), sleep(1), sleep(5), sleep(4), sleep(2) ]).then((result) => { console.log(result); }); } main();
wait: 1 sec wait: 2 sec wait: 3 sec wait: 4 sec wait: 5 sec [ 3, 1, 5, 4, 2 ]
Promise.all()
という関数を使うと複数の処理が終わるのを待つことができます。
then()
に渡される値はPromise.all()
に渡した順番と一致します。
async/awaitの使い方
function sleep(sec) { return new Promise((resolve) => { setTimeout(() => { console.log(`wait: ${sec} sec`); resolve(sec); }, sec * 1000); }); } function raise() { return new Promise((_, reject) => { reject(new Error("failed")); }); } async function sum(a, b) { return a + b; } async function main() { const sec = await sleep(2); console.log(`result: ${sec} sec`); try { await raise(); } catch (e) { console.log(`error: ${e}`); } const not_await = sum(1, 2); console.log(`not await: ${not_await}`); const did_await = await sum(2, 3); console.log(`did await: ${did_await}`); } main()
$ node main.js wait: 2 sec result: 2 sec error: Error: failed not await: [object Promise] did await: 5
async/awaitというのはPromiseをもっと便利に扱うための機構です。
asyncと修飾した関数内でPromiseオブジェクトにawaitをつけることで、コンストラクタに渡した処理が完了するまで待ちます。
resolve
に渡した内容が返り値に、reject
に渡した内容はthrowされます。
また、asyncと修飾した関数はreturnで値を返していてもPromiseオブジェクトでラップされます。
そのため、awaitをつけることでreturnされた値を取得することができます。
まとめ
以上、Promiseとasync/awaitの使い方でした。
よく使うけどわかりにくい機能なので、理解の一助になれば幸いです。
雑に説明しただけなので、詳しく知りたいときは次のドキュメントなどで確認してください。
おまけ) 今は昔、Callback地獄といふものありけり
function sleep(sec, callback) { setTimeout(function() { console.log("wait: " + sec + " sec"); callback(sec); }, sec * 1000); } function main() { var sec0 = 1; sleep(sec0, function(sec1) { sleep(sec0 + sec1, function(sec2) { sleep(sec1 + sec2, function(sec3) { sleep(sec2 + sec3, function(sec4) { sleep(sec3 + sec4, function(sec5) { console.log("last wait time: " + sec5 + "sec"); }); }); }); }); }); } main();
wait: 1 sec wait: 2 sec wait: 3 sec wait: 5 sec wait: 8 sec last wait time: 8sec